#!/usr/bin/env python
# -*- coding: utf8 -*-
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.

from collections import defaultdict
from dataclasses import dataclass, asdict, field
from pathlib import Path
from typing import Set, Union
import glob

import click
import frontmatter
try:
    import ruamel.yaml as yaml
except ImportError:
    import ruamel_yaml as yaml

namespace_comment = "### YamlMime:QSharpNamespace"
warning_comment = """
# This file is automatically generated.
# Please do not modify this file manually, or your changes may be lost when
# documentation is rebuilt.
"""

@dataclass(frozen=True)
class NamespaceItem:
    summary: str = ""
    name: str = ""
    namespace: str = ""
    uid: str = ""
    kind: str = ""

@dataclass
class Namespace:
    summary: str = ""
    uid: str = ""
    name: str = ""
    # NB: We need to set default_factory instead of default, since set is a
    #     mutable type.
    items: Set = field(default_factory=set)

def items_of_kind(items, kind):
    return [
        {
            "uid": item.uid,
            "name": item.name,
            "summary": item.summary
        }
        for item in
        sorted(
            (item for item in items if item.kind == kind),
            key=(lambda item: item.uid)
        )
    ]

def summaries_table(namespace : Namespace, kind : str, header : str) -> str:
    table_header = f"""
## {header}

| Name | Summary |
|------|---------|
"""
    def first_line(summary : str) -> str:
        lines = summary.splitlines()
        return lines[0] if lines else ""

    items = items_of_kind(namespace.items, kind)
    return (
        table_header + "\n".join(
            f"|[{item['name']}](xref:{item['uid']}) |{first_line(item['summary'])} |"
            for item in items
        )
        if items
        else ""
    )

def write_namespace_summary(target_file : Path, namespace : Namespace) -> None:
    """
    Given the contents of a namespace, either creates a new Markdown file
    summarizing those contents, or adds those contents to an existing Markdown
    file, overwriting any existing summary tables.
    """

    begin_region = "<!-- summaries -->"
    end_region = "<!-- /summaries -->"

    # Make the summary table to be injected.
    summaries = begin_region + "\n" + "\n".join([
        summaries_table(namespace, 'operation', 'Operations'),
        summaries_table(namespace, 'function', 'Functions'),
        summaries_table(namespace, 'udt', 'User-defined types'),
    ]) + "\n" + end_region + "\n"


    # Check if the file already exists
    if target_file.exists():
        contents = target_file.read_text(encoding='utf8')
        # If it does exist, look for <!-- summaries --> and <!-- /summaries -->
        # comments, and if they exist, replace inside that section.
        if begin_region in contents:
            contents = contents[:contents.index(begin_region)] + summaries + contents[contents.index(end_region) + len(end_region):]
        else:
            contents += "\n" + summaries

        with open(target_file, 'w', encoding='utf8') as f:
            f.write(contents)

    # If the file does not exist, go on and make it now.
    else:
        with open(target_file, 'w', encoding='utf8') as f:
            f.write(f"""
---
uid: {namespace.uid}
title: {namespace.name} namespace
ms.topic: managed-reference
qsharp.kind: namespace
qsharp.name: {namespace.name}
qsharp.summary: ""
---

# {namespace.name} Namespace

{summaries}

""")

@click.command()
@click.argument("sources")
@click.argument("output_path")
def main(sources : str, output_path : Union[str, Path]):
    namespaces: dict = defaultdict(Namespace)
    output_path = Path(output_path)
    for source in glob.glob(sources):
        print(source)
        with open(source, 'r', encoding='utf8') as f:
            header, _ = frontmatter.parse(f.read())
        if header['qsharp.kind'] == 'namespace':
            namespaces[header['qsharp.name']].summary = header['qsharp.summary']
            namespaces[header['qsharp.name']].name = header['qsharp.name']
            namespaces[header['qsharp.name']].uid = header['uid']
        else:
            namespaces[header['qsharp.namespace']].items.add(NamespaceItem(
                summary=header['qsharp.summary'],
                name=header['qsharp.name'],
                namespace=header['qsharp.namespace'],
                uid=header["uid"],
                kind=header["qsharp.kind"]
            ))

    for namespace_name, namespace in namespaces.items():
        write_namespace_summary(output_path / f"{namespace_name.lower()}.md", namespace)

    toc_page = [
        {
            "uid": namespace.name,
            "name": namespace_name,
            "items": [
                {
                    "name": item.name,
                    "uid": item.uid
                }
                for item in sorted(
                    namespace.items,
                    key=lambda item: item.uid
                )
            ]
        }
        for namespace_name, namespace in sorted(
            namespaces.items(),
            key=lambda pair: pair[0]
        )
    ]
    with open(output_path / "toc.yml", "w", encoding="utf8") as f:
        f.write(warning_comment + yaml.dump(toc_page))

if __name__ == "__main__":
    main()
